﻿using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Text;
using DataScripter.Database;
using DataScripter.Scripting.OutputDestinations;

namespace DataScripter.Scripting.Output.Sql.SqlServer
{
    public abstract class SqlServerOutputProducer : IOutputProducer
    {
        public IOutputDestination OutputDestination { get; private set; }
        private IScriptResult _scriptResult;

        protected SqlServerOutputProducer(IOutputDestination outputDestination)
        {
            OutputDestination = outputDestination;
        }

        public void InitializeScript(IEngine engine, IScriptResult scriptResult)
        {
            _scriptResult = scriptResult;
            _scriptResult.Writer.WriteLine("SET XACT_ABORT ON");
            _scriptResult.Writer.WriteLine("SET NOCOUNT ON");
            _scriptResult.Writer.WriteLine("BEGIN TRANSACTION");
        }

        public void FinalizeScript()
        {
            _scriptResult.Writer.WriteLine();
            _scriptResult.Writer.WriteLine("COMMIT TRANSACTION");
        }

        public virtual void ProduceOutput(IDataReader reader, ITableScriptRequest tableScriptRequest)
        {
            TableScriptRequest = tableScriptRequest;

            StreamWriter streamWriter = _scriptResult.Writer;
            streamWriter.WriteLine("/*");
            streamWriter.WriteLine(string.Format("TABLE: {0}", tableScriptRequest.TableName));
            streamWriter.WriteLine(string.Format("QUERY: {0}", tableScriptRequest.UsedQuery));
            streamWriter.WriteLine("*/");
            bool hasRecords = reader.Read();
            if (!hasRecords)
            {
                streamWriter.WriteLine("--No records for " + tableScriptRequest.TableName);
            }
            else
            {
                OutputContext = new SqlScriptOutputContext();

                ScriptBeforeFirstRecord(tableScriptRequest, streamWriter);
                ScriptAllRecords(reader, streamWriter);
                ScriptAfterLastRecord(tableScriptRequest, streamWriter);
            }
        }

        private void ScriptAllRecords(IDataReader reader, StreamWriter streamWriter)
        {
            bool isFirstRecord = true;
            do
            {
                HandleRecord(reader, streamWriter, isFirstRecord);
                isFirstRecord = false;
            } 
            while (reader.Read());
        }

        private static void ScriptAfterLastRecord(ITableScriptRequest tableScriptRequest, StreamWriter streamWriter)
        {
            if (tableScriptRequest.TableStructure.HasIdentityFields)
            {
                streamWriter.WriteLine("SET IDENTITY_INSERT dbo." + tableScriptRequest.TableName + " OFF");
            }
        }

        protected virtual void ScriptBeforeFirstRecord(ITableScriptRequest tableScriptRequest, StreamWriter streamWriter)
        {
            if (tableScriptRequest.TableStructure.HasIdentityFields)
            {
                streamWriter.WriteLine("SET IDENTITY_INSERT dbo." + tableScriptRequest.TableName + " ON");
            }
        }

        protected ITableScriptRequest TableScriptRequest { get; private set; }

        protected SqlScriptOutputContext OutputContext { get; private set; }

        protected abstract void HandleRecord(IDataReader reader, StreamWriter streamWriter, bool isFirstRecord);

        protected static void ScriptTableNamePkFound(ITableScriptRequest tableScriptRequest, IDataReader reader, StreamWriter streamWriter)
        {
            IList<FieldInfo> primaryKeyFields = tableScriptRequest.TableStructure.PrimaryKeyFields;
            string pkValueRaw = primaryKeyFields[0].ReadRawValue(reader).ToString();

            streamWriter.WriteLine("--" + tableScriptRequest.TableName + ": Found " + pkValueRaw);
        }

        protected static void ScriptSpecialFieldTreatments(ITableScriptRequest tableScriptRequest, IDataReader reader, StreamWriter streamWriter, SqlScriptOutputContext outputContext, Db db)
        {
            foreach (ISpecialFieldTreatment specialFieldTreatment in tableScriptRequest.SpecialFieldTreatments.Values)
            {
                specialFieldTreatment.Initialize(reader, db, outputContext);
                specialFieldTreatment.Script(streamWriter);
            }
        }

        protected virtual string ProduceInsertSqlForCurrentRecord(ITableScriptRequest tableScriptRequest, IDataReader reader, bool isFirstRecord)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(ProduceInsertClause(tableScriptRequest, isFirstRecord));
            sb.Append(ProduceValuesSqlForCurrentRecord(tableScriptRequest, reader, isFirstRecord));
            return sb.ToString();
        }

        protected virtual string ProduceInsertClause(ITableScriptRequest tableScriptRequest, bool isFirstRecord)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("INSERT INTO " + tableScriptRequest.TableName + "(");
            sb.Append(string.Join(",", tableScriptRequest.TableStructure.Fields.Values.Select(f => f.ColumnName).ToArray()));
            sb.Append(") ");
            return sb.ToString();
        }

        protected virtual string ProduceValuesSqlForCurrentRecord(ITableScriptRequest tableScriptRequest, IDataReader reader, bool isFirstRecord)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("VALUES (");

            foreach (FieldInfo field in tableScriptRequest.TableStructure.Fields.Values)
            {
                if (tableScriptRequest.SpecialFieldTreatments.ContainsKey(field.ColumnName))
                {
                    sb.Append(tableScriptRequest.SpecialFieldTreatments[field.ColumnName].VariableName);
                }
                else
                {
                    sb.Append(field.ReadValue(reader));
                }
                sb.Append(",");
            }
            sb.Remove(sb.Length - 1, 1);
            sb.Append(")");
            return sb.ToString();
        }
    }
}
